package com.yelink.metrics.core;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.yelink.metrics.core.constant.annotations.MetricsView;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Component;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.yelink.metrics.api.MetricsApi;
import com.yelink.metrics.api.dto.MetricsUpsertDTO;
import com.yelink.metrics.api.dto.TableColumnDTO;
import com.yelink.metrics.api.dto.TargetDTO;
import com.yelink.metrics.config.MetricsParam;
import com.yelink.metrics.core.constant.Constant;
import com.yelink.metrics.core.constant.ModelType;
import com.yelink.metrics.core.constant.annotations.MetricsColumn;
import com.yelink.metrics.core.constant.annotations.MetricsInfo;
import com.yelink.metrics.core.constant.enums.DateSourceType;
import com.yelink.metrics.core.constant.enums.FieldDefine;
import com.yelink.metrics.core.constant.enums.FieldType;
import com.yelink.metrics.core.constant.enums.TimeDimension;
import com.yelink.metrics.core.domain.BaseMetricsEntity;
import lombok.extern.slf4j.Slf4j;

/**
 * 每次运行都会初始化/更新的方法
 * @author idea
 */
@Slf4j
@Component
public class MetricsConfigInitializer implements ApplicationRunner {

    @Resource
    private MetricsApi metricsApi;
    @Resource
    private MetricsParam metricsParam;

    @Override
    public void run(ApplicationArguments args) {

        try {
            ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
            scanner.addIncludeFilter(new AnnotationTypeFilter(MetricsInfo.class));
            Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(metricsParam.getBasePackage());
            List<TableColumnDTO> tableColumns = new ArrayList<>();
            List<TargetDTO> targets = new ArrayList<>();
            Set<String> duplicateTargetNameSet = new HashSet<>();
            Set<String> duplicateTableColumnSet = new HashSet<>();

            for (BeanDefinition beanDefinition : beanDefinitions) {
                Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
                // 获取表注释
                MetricsInfo metricsInfoAnnotation = clazz.getAnnotation(MetricsInfo.class);
                if(metricsInfoAnnotation == null) {
                    log.warn("【metrics初始化】class:{} 没有@MetricsInfo, 跳过...", clazz.getName());
                    continue;
                }
                // 获取mp标记的表名
                TableName tableNameAnnotation = clazz.getAnnotation(TableName.class);
                if(tableNameAnnotation == null) {
                    log.warn("【metrics初始化】class:{} 没有@TableName, 跳过...", clazz.getName());
                    continue;
                }
                // 获取模型
                ModelType modelType;
                try {
                    modelType = MetricsUtils.resolveModelType(metricsInfoAnnotation);
                }catch (Exception e) {
                    log.warn("【metrics初始化】class:{} MetricsInfo无法获取ModelType, 跳过..., msg::{}", clazz.getName(), e.getMessage());
                    continue;
                }
                String metricsComment = metricsInfoAnnotation.comment();
                String tableName = tableNameAnnotation.value();
                String schema = tableNameAnnotation.schema();
                // 1.指标 + 表
                TargetDTO target = new TargetDTO();
                target.setModelCode(modelType.getCode());
                target.setModelName(modelType.getName());
                //      指标名、表名、触发方法名 直接一样
                target.setTableSchema(schema);
                target.setTableName(tableName);
                target.setTargetName(tableName);
                target.setTargetCname(metricsComment);
                // 视图
                MetricsView metricsView = clazz.getAnnotation(MetricsView.class);
                if(metricsView == null) {
                    target.setMethodName(Constant.SDK_METHOD_NAME);
                    target.setMethodCname(Constant.SDK_METHOD_CNAME);
                    target.setFrequency(metricsInfoAnnotation.defaultFrequency());
                    target.setFrequencyUnit(metricsInfoAnnotation.defaultFrequencyUnit());
                    target.setDataSourceType(DateSourceType.INNER.code);
                }else {
                    target.setSql(metricsView.sql());
                    target.setDataSourceType(DateSourceType.VIEW.code);
                    target.setMethodName("");
                    target.setMethodCname("");
                }
                //      校验重复
                if(!duplicateTargetNameSet.add(tableName)) {
                    log.warn("【metrics初始化】class:{}, 指标名：{} 出现重复, 请检查代码, 跳过...", clazz.getName(), tableName);
                    continue;
                }
                targets.add(target);

                // 2. 字段
                TableColumnDTO baseDto = new TableColumnDTO();
                baseDto.setTableName(tableName);
                baseDto.setTableSchema(schema);
                baseDto.setTableRemark(metricsComment);
                // 自身及父类的成员变量
                List<Field> fields = MetricsUtils.getAllInstanceFieldsParentFirst(clazz);
                for (Field field : fields) {
                    TableField tableField = field.getAnnotation(TableField.class);
                    // 跳过不需要映射到数据库的字段
                    if (tableField != null && !tableField.exist()) {
                        continue;
                    }
                    // 字段编码
                    String fieldCode = getFieldCode(field);
                    if (StrUtil.isBlank(fieldCode)) {
                        continue;
                    }
                    TableColumnDTO dto = BeanUtil.copyProperties(baseDto, TableColumnDTO.class);
                    // 特殊字段处理
                    if("id".equals(fieldCode)) {
                        continue;
                    }
                    if("uni_code".equals(fieldCode)) {
                        dto.setFieldRemark(BaseMetricsEntity.buildUniCodeFormat(clazz));
                    }
                    dto.setFieldCode(fieldCode);
                    // 字段中文： MetricsColumn 注解中获取  -->  取fieldCode
                    // 字段备注： MetricsColumn 注解中获取(如timeEnum有效，优先从这里获取)
                    // 字段定义： MetricsColumn 注解中获取  -->  从字段拼写推断
                    // 字段类型： MetricsColumn 注解中获取  -->  从java类型推断
                    FieldType fieldType = null;
                    MetricsColumn metricsColumn = field.getAnnotation(MetricsColumn.class);
                    if(metricsColumn != null) {
                        dto.setFieldName(metricsColumn.comment());
                        dto.setFieldRemark(metricsColumn.remark());
                        // 字段定义：
                        FieldDefine fieldDefine = metricsColumn.filedDefine();
                        if(fieldDefine != FieldDefine.AUTO) {
                            dto.setFieldDefine(fieldDefine);
                            if(fieldDefine == FieldDefine.TIME_DIMENSION) {
                                TimeDimension timeDimension = metricsColumn.timeEnum();
                                if(timeDimension == TimeDimension.NONE) {
                                    log.warn("【metrics初始化】class:{}, 字段：{} 定义为时间维度, 但没有时间单位, 请检查代码, 本次不跳过...", clazz.getName(), fieldCode);
                                }
                                dto.setFieldRemark(timeDimension.remark);
                            }
                        }
                        // 字段类型
                        FieldType columnFieldType = metricsColumn.filedType();
                        if(columnFieldType != FieldType.AUTO) {
                            fieldType = columnFieldType;
                        }
                    }
                    // 二次赋予：名称
                    if(StrUtil.isBlank(dto.getFieldName())) {
                        dto.setFieldName(fieldCode);
                    }
                    // 二次赋予：定义
                    if(dto.getFieldDefine() == null) {
                        dto.setFieldDefine(inferFieldDefine(fieldCode));
                    }
                    // 二次赋予：类型
                    if(fieldType == null) {
                        fieldType = inferFieldType(field.getType());
                    }
                    dto.setFieldType(fieldType.getType());
                    dto.setFieldPoint(fieldType.getDefaultPoint());
                    dto.setFieldLength(fieldType.getDefaultLength());
                    // 校验重复
                    if(!duplicateTableColumnSet.add(dto.buildUniCode())) {
                        log.warn("【metrics初始化】class:{}, 标识符：{} 出现重复, 请检查代码, 跳过...", clazz.getName(), dto.buildUniCode());
                        continue;
                    }
                    tableColumns.add(dto);
                }
            }
            metricsApi.refreshMetrics(
                    MetricsUpsertDTO.builder().targets(targets).tableColumns(tableColumns).module(metricsParam.getModule()).build()
            );
        } catch (Exception e) {
            log.error("【metrics初始化】遇到错误", e);
        }
    }

    /**
     * 获取字段的数据库列名
     */
    private String getFieldCode(Field field) {
        TableField tableField = field.getAnnotation(TableField.class);
        if (tableField != null) {
            return Optional.ofNullable(tableField.value()).map(e -> e.replaceAll("`", "")).orElse(null);
        }
        // 如果没有TableField注解，使用字段名的下划线形式
        return StrUtil.toUnderlineCase(field.getName());
    }

    private FieldType inferFieldType(Class<?> fieldType) {
        if (Integer.class.isAssignableFrom(fieldType) || int.class.isAssignableFrom(fieldType)) {
            return FieldType.INT;
        } else if (Double.class.isAssignableFrom(fieldType) || double.class.isAssignableFrom(fieldType)) {
            return FieldType.DOUBLE;
        } else if (java.util.Date.class.isAssignableFrom(fieldType)
                || java.sql.Date.class.isAssignableFrom(fieldType)
                || LocalDateTime.class.isAssignableFrom(fieldType)
                || LocalDate.class.isAssignableFrom(fieldType)
        ) {
            return FieldType.DATETIME;
        } else {
            return FieldType.VARCHAR;
        }
    }

    private FieldDefine inferFieldDefine(String fieldCode) {
        if (fieldCode.endsWith("quantity")
                || fieldCode.endsWith("rate")
                || fieldCode.endsWith("count")
                || fieldCode.endsWith("hour")
                || fieldCode.endsWith("hours")
                || fieldCode.endsWith("rank")
                || fieldCode.endsWith("val")
                || fieldCode.endsWith("value")
        ) {
            return FieldDefine.TARGET;

        } else {
            return FieldDefine.ATTACHMENT;
        }
    }

}
